import { apply, applyTemplates, chain, mergeWith, move, Rule, SchematicContext, Tree, url } from '@angular-devkit/schematics';
import { strings } from "@angular-devkit/core";
import { plural } from "pluralize";
import * as path from 'path'
import * as ts from 'typescript'
export function generaterList(_options: any): Rule {
  return generaterFiles(_options,'list')
}
export function generaterTree(_options: any): Rule {
  return generaterFiles(_options,'tree')
}
export function generaterFiles(_options: any, templatesPath: string): Rule {
  return (_tree: Tree, _context: SchematicContext) => {
    const entityName = _options.name
    const title = _options.path
    const sourceTemplateRules = apply(url(`./${templatesPath}/src`), 
      [
        applyTemplates({
          entityName,
          plural,
          title,
          ...strings
        }),
        move(path.normalize('src'))
      ]
    )
    const viewsTemplateRules = apply(url(`./${templatesPath}/views`), 
      [
        applyTemplates({
          entityName,
          plural,
          title,
          ...strings
        }),
        move(path.normalize('views'))
      ]
    )

    return chain([
      mergeWith(sourceTemplateRules),
      mergeWith(viewsTemplateRules),
      updateAdminModule(entityName),
      updateSharedModule(entityName)
    ]);
  };
}

function updateSharedModule(entityName: string){
  return (tree: Tree) => {
    const adminModulePath = 'src/shared/shared.module.ts'
    const sourceFile = getSourceFile(tree, adminModulePath)
    if (sourceFile) {
      const {classifiedName, dasherizeName} = getClassifiedAndDasherizeName(entityName)
      const update = [
        addImportToModule(classifiedName, `./entities/${dasherizeName}.entity`),
        addImportToModule(`${classifiedName}Service`, `./services/${dasherizeName}.service`),
        addToModuleArray('providers', `${classifiedName}Service`),
        addToModuleArray('exports', `${classifiedName}Service`),
        addToMethodArray('forFeature', classifiedName),
      ]
      applyTransformationAndSave(tree, adminModulePath,sourceFile,update)
    }
    return tree
  }
}
function addToMethodArray(methodName: string, resourceName: string):ts.TransformerFactory<ts.SourceFile>{
  return (_context:ts.TransformationContext) => (rootNode:ts.SourceFile) => {
    function visitor(node:ts.Node): ts.Node{
      if (
        ts.isCallExpression(node)&&
        ts.isPropertyAccessExpression(node.expression)&&
        node.expression.name.text === methodName&&
        node.arguments.length === 1&&
        ts.isArrayLiteralExpression(node.arguments[0])
      ){
        const elements = [...node.arguments[0].elements, ts.factory.createIdentifier(resourceName)]
        return ts.factory.updateCallExpression(
          node, 
          node.expression, 
          node.typeArguments,
          [ts.factory.createArrayLiteralExpression(elements)]
        )
      }
      return ts.visitEachChild(node,visitor,_context)
    }
    return ts.visitNode(rootNode, visitor) as ts.SourceFile
  }
}
// 用来更新src\admin\admin.module.ts
function updateAdminModule(entityName: string){
  return (tree: Tree) => {
    const adminModulePath = 'src/admin/admin.module.ts'
    const sourceFile = getSourceFile(tree, adminModulePath)
    if (sourceFile) {
      const {classifiedName, dasherizeName} = getClassifiedAndDasherizeName(entityName)
      const update = [
        addImportToModule(`${classifiedName}Controller`, `./controllers/${dasherizeName}.controller`),
        addToModuleArray('controllers', `${classifiedName}Controller`),
      ]
      applyTransformationAndSave(tree, adminModulePath,sourceFile,update)
    }
    return tree
  }
}
function addToModuleArray (arrayNme: string,itemName: string):ts.TransformerFactory<ts.SourceFile> {
  return (_context:ts.TransformationContext) => (rootNode:ts.SourceFile) => {
    function visitor(node:ts.Node): ts.Node{
      if (
        ts.isPropertyAssignment(node)&&ts.isIdentifier(node.name)&&node.name.text == arrayNme
        &&ts.isArrayLiteralExpression(node.initializer)
      ){
        const elements = [...node.initializer.elements.map(ele => ele.getText()),itemName]
        return ts.factory.updatePropertyAssignment(node, node.name,ts.factory.createArrayLiteralExpression(elements.map(ele => ts.factory.createIdentifier(ele))))
      }
      return ts.visitEachChild(node,visitor,_context)
    }
    return ts.visitNode(rootNode, visitor) as ts.SourceFile
  }
}
// 更新保存文件
function applyTransformationAndSave(
  tree: Tree, filePath: string,
  sourceFile: ts.SourceFile,
  transformations: Array<ts.TransformerFactory<ts.SourceFile>>
){
  const updateSourceFile = ts.transform(sourceFile, transformations).transformed[0]
  tree.overwrite(filePath, ts.createPrinter().printFile(updateSourceFile))
}
function addImportToModule(importName: string, importPath: string){
  return (_context: any) => (rootNode:ts.SourceFile) => {
    const lastImport = rootNode.statements.filter(ts.isImportDeclaration).pop()
    const nweimport = ts.factory.createImportDeclaration(
      undefined,
      ts.factory.createImportClause(
        false,
        undefined,
        ts.factory.createNamedImports([
          ts.factory.createImportSpecifier(false, undefined,ts.factory.createIdentifier(importName))
        ])
      ),
      ts.factory.createStringLiteral(importPath)
    )
    const updateStatements = ts.factory.createNodeArray([
      ...rootNode.statements.slice(0, rootNode.statements.indexOf(lastImport!)+1),
      nweimport,
      ...rootNode.statements.slice(rootNode.statements.indexOf(lastImport!)+1),
    ])
    return ts.factory.updateSourceFile(rootNode, updateStatements)
  }
}
function getClassifiedAndDasherizeName(name: string){
  return {
    classifiedName: strings.classify(name), // 获取类名
    dasherizeName: strings.dasherize(name) // 获取破折号
  }
}
function getSourceFile (tree: Tree, filePath: string):ts.SourceFile{
  const content = tree.read(filePath)?.toString('utf-8')
  return ts.createSourceFile(filePath, content!,ts.ScriptTarget.Latest,true)
}